//
// Created by 史振江 on 7/30/23.
//

#include <rtmp.h>
#include <faac.h>
#include "SVideoChannel.h"

SVideoChannel::SVideoChannel() {
    pthread_mutex_init(&mutex, nullptr);
}

SVideoChannel::~SVideoChannel() {
    pthread_mutex_destroy(&mutex);
}


void SVideoChannel::initX264Encode(int width, int height, int fps, int bitrate) {
    pthread_mutex_lock(&mutex);

    this->width = width;
    this->height = height;
    this->fps = fps;
    this->bitrate = bitrate;

    y_len = width * height;
    uv_len = y_len / 4;

    // 防止重复初始化
    if (videoEncode) {
        x264_encoder_close(videoEncode);
        videoEncode = nullptr;
    }

    if (pic_in) {
        x264_picture_clean(pic_in);
        DELETE(pic_in)
    }

    // x264的参数集
    x264_param_t param;


    // 设置编码器属性
    // 参数二: { "ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow", "placebo", 0 };
    // 参数三: { "film", "animation", "grain", "stillimage", "psnr", "ssim", "fastdecode", "zerolatency", 0 }
    x264_param_default_preset(&param, "ultrafast", "zerolatency");

    param.i_level_idc = 32; // 设置编码规格

    // 输入数据格式
    // - YUV420P (P = 平面格式)
    // YUV420 交错模式
    param.i_csp = X264_CSP_I420;
    param.i_width = width;
    param.i_height = height;

    // 不能存在B帧， B帧会影响效率
    param.i_bframe = 0;

    // 码率控制方式: CQP(恒定质量); CRF(恒定码率); ABR(平均码率)
    param.rc.i_rc_method = X264_RC_CRF;

    // 设置码率
    param.rc.i_bitrate = bitrate / 1000;

    // 瞬时最大码率 (网络波动导致)
    param.rc.i_vbv_max_bitrate = param.rc.i_bitrate * 1.2;

    // 设置buffer大小，码率控制区大小
    param.rc.i_vbv_buffer_size = param.rc.i_bitrate;

    // 码率的控制不通过timebase， 使用fps来控制
    param.b_vfr_input = 0;

    // 帧率的分子部分，表示每秒显示的帧数。
    param.i_fps_num = fps;

    // 帧率的分母部分，表示每秒的时间单位数量
    param.i_fps_den = 1;
    // 表示1秒 ${fps} 帧

//    i_timebase_den: 表示时间基准的分母，用于表示每个时间单位的时长。通常情况下，它的默认值为1。
    param.i_timebase_den = param.i_fps_num;

    //    i_timebase_num: 表示时间基准的分子，用于表示时间单位的数量。通常情况下，它的默认值为帧率的分子部分。
    param.i_timebase_num = param.i_fps_den;
//    param.i_timebase_den = 1;
//    param.i_timebase_num = 1000;


    // 设置关键帧的最大举例
    param.i_keyint_max = fps * 2;

    // sps 序列参数集 (解码使用)
    // pps 图像参数集 (解码使用)
    // 是否将 sps 和 pps 放到关键帧的前面，【该参数将每个关键帧都附带（sps/pps）】
    param.b_repeat_headers = true;

    // 并行线程数  0编码器将自动选择合适的线程数，默认情况下是根据系统的CPU核心数来确定线程数。
    param.i_threads = 1;

    // 将上面的参数提交
    // { "baseline", "main", "high", "high10", "high422", "high444", 0 }
    x264_param_apply_profile(&param, "baseline");

    // 输入图片初始化
    pic_in = new x264_picture_t;
    // 初始化
    x264_picture_alloc(pic_in, param.i_csp, param.i_width, param.i_height);

    // 打开编码器
    videoEncode = x264_encoder_open(&param);
    if (videoEncode) {
        LOG_D("X264编码器打开成功")
    }

    pthread_mutex_unlock(&mutex);
}

// 编码工作
void SVideoChannel::encodeData(signed char *data) {
    pthread_mutex_lock(&mutex);

    // 将NV21 的Y copy 到 I420 的 Y， 因为Y是相同的
    // y 数据
    memcpy(pic_in->img.plane[0], data, y_len);

    for (int i = 0; i < uv_len; ++i) {
        // u 数据
        *(pic_in->img.plane[1] + i) = *(data + y_len + i * 2 + 1);

        // v 数据
        *(pic_in->img.plane[2] + i) = *(data + y_len + i * 2);
    }

    // 通过X264编码的到NAL数组
    x264_nal_t *nal = nullptr;

    // NAL单元的数量
    int pi_nal;

    // 编码后的图片
    x264_picture_t pic_out;

    // 1. 视频编码器
    // 2. nal
    // 3. NAL单元的数量
    // 4. 输入原始的图片
    // 5. 输出编码后的图片
    int ret = x264_encoder_encode(videoEncode, &nal, &pi_nal, pic_in, &pic_out);// 本次编码一张图片
    if (ret < 0) {// 编码成功返回NAL的数量， 返回0或负数表示返回失败
        LOG_D("编码失败")
        pthread_mutex_unlock(&mutex);
        return;
    }

    // sps/pps 告诉编码器如何解析数据
    int sps_len, pps_len;

    // 用于接收sps与pps的数组
    uint8_t sps[100];
    uint8_t pps[100];

    // pts显示的时间
    // dts 编码时间
    pic_in->i_pts += 1;

//    LOG_D("编码器成功 pi_nal = %d", pi_nal)
    for (int i = 0; i < pi_nal; ++i) {
        if (nal[i].i_type == NAL_SPS) {
            // 去掉起始码,
            sps_len = nal[i].i_payload - 4;
            memcpy(sps, nal[i].p_payload + 4, sps_len);
        } else if (nal[i].i_type == NAL_PPS) {
            //
            pps_len = nal[i].i_payload - 4;
            memcpy(pps, nal[i].p_payload + 4, pps_len);

            // 有了pps，一定执行了sps，因为sps在pps前面
            sendSpsWithPPS(sps, pps, sps_len, pps_len);
        } else {
            // 发送I帧与 P帧
            sendIWithP(nal[i].i_type, nal[i].i_payload, nal[i].p_payload);
        }
    }

    pthread_mutex_unlock(&mutex);
}

// TODO 发送 i帧 p帧
void SVideoChannel::sendIWithP(int type, int payload, uint8_t *pPayload) {
    //

    if (pPayload[2] == 0x00) {
        pPayload += 4;
        payload -= 4;
    } else if (pPayload[2] == 0x01) {
        pPayload += 3;
        payload -= 3;
    }

    auto *packet = new RTMPPacket;

    int body_size = 5 + 4 + payload;
    // 开辟空间
    RTMPPacket_Alloc(packet, body_size);

    packet->m_body[0] = 0X27;
    // 关键帧 = 0x17;
    // 非关键帧(P帧) = 0x27;
    if (type == NAL_SLICE_IDR) {
        packet->m_body[0] = 0X17;
    }

    packet->m_body[1] = 0x01; // 表示不是sps或pps，是帧类型
    packet->m_body[2] = 0x00;
    packet->m_body[3] = 0x00;
    packet->m_body[4] = 0x00;

    packet->m_body[5] = (payload >> 24) & 0xFF;
    packet->m_body[6] = (payload >> 16) & 0xFF;
    packet->m_body[7] = (payload >> 8) & 0xFF;
    packet->m_body[8] = payload & 0xFF;

    memcpy(&packet->m_body[9], pPayload, payload);

    // 封包处理
    packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; // 视频类型
    packet->m_nBodySize = body_size; // sps + pps 大小
    packet->m_nChannel = 10; // 通道id
    packet->m_nTimeStamp = -1; // 手动计算时间戳
    packet->m_hasAbsTimestamp = 0; // 没有时间戳

    // 包的类型，设置为大包
    packet->m_headerType = RTMP_PACKET_SIZE_LARGE;

    // 吧帧类型存入队列
    if (videoCallBack != nullptr)
        videoCallBack(packet);
}


// TODO 发送sps 与 pps
void SVideoChannel::sendSpsWithPPS(uint8_t *sps, uint8_t *pps, int sps_len, int pps_len) {

    int body_size = 5 + 8 + sps_len + 3 + pps_len;

    // 开始封包
    auto *packet = new RTMPPacket;

    // 初始化包
    RTMPPacket_Alloc(packet, body_size);


    int i = 0;
    packet->m_body[i++] = 0x17;

    packet->m_body[i++] = 0x00;
    packet->m_body[i++] = 0x00;
    packet->m_body[i++] = 0x00;
    packet->m_body[i++] = 0x00;

    // 版本
    packet->m_body[i++] = 0x01;

    // sps
    packet->m_body[i++] = sps[1];
    packet->m_body[i++] = sps[2];
    packet->m_body[i++] = sps[3];

    packet->m_body[i++] = 0xFF;

    packet->m_body[i++] = 0xE1; // sps个数

    // sps长度 2个字节表示
    packet->m_body[i++] = (sps_len >> 8) & 0xFF; // 取高8位
    packet->m_body[i++] = sps_len & 0xFF; // 取低8位

    // sps 内容
    memcpy(&packet->m_body[i], sps, sps_len);
    i += sps_len;

    // pps个数 用一个字节表示
    packet->m_body[i++] = 0x01;

    // pps长度
    packet->m_body[i++] = (pps_len >> 8) & 0xFF; // 取高8位
    packet->m_body[i++] = pps_len & 0xFF; // 取低8位

    // pps 内容
    memcpy(&packet->m_body[i], pps, pps_len);
    i += pps_len;

    // 封包处理
    packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; // 视频类型
    packet->m_nBodySize = body_size; // sps + pps 大小
    packet->m_nChannel = 10; // 通道id
    packet->m_nTimeStamp = 0; // sps与pps没有时间戳
    packet->m_hasAbsTimestamp = 0; // 没有时间戳

    // 包的类型，数据量较少 设置中包即可
    packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;

    if (videoCallBack != nullptr)
        videoCallBack(packet);
}

void SVideoChannel::setVideoCallBack(VideoCallBack v) {
    this->videoCallBack = v;
}


